/*****************************************************************************/
/*      (c) Copyright 2006 Rapid Deployment Software - See License.txt       */
/*****************************************************************************/
/*                                                                           */
/*                       SCREEN OUTPUT HANDLER                               */
/*                                                                           */
/*****************************************************************************/

/******************/
/* Included files */
/******************/

#include <stdio.h>

#ifdef ELINUX
# include <sys/stat.h>
# include <unistd.h>
#endif
#ifdef EWINDOWS
# include <sys\types.h>
# include <sys\stat.h>
# if !defined(ELCC) && !defined(EBORLAND)
#  include <graph.h>
# endif
# include <windows.h>
#endif

#include "global.h"
#include "execute.h"

/******************/
/* Local defines  */
/******************/

#define MAX_SCREEN_WIDTH 200 /* what if resolutions get really high? >1280 */
#define TAB_WIDTH 8          /* power of 2 assumed */

/**********************/
/* Imported variables */
/**********************/

extern int col_max;
extern int con_was_opened;
extern int current_screen;
extern int line_max;

extern struct videoconfig config;

extern unsigned current_bg_color;
extern unsigned current_fg_color;

/**********************/
/* Exported variables */
/**********************/

#ifdef EWINDOWS
HANDLE console_input;  // HANDLE for WIN32 console input
HANDLE console_output; // HANDLE for WIN32 console output
HANDLE console_trace;  // HANDLE for WIN32 output to trace-screen
HANDLE console_var_display; // HANDLE for WIN32 output to large sequence display
#endif

char *collect = NULL;    /* to collect sprintf/sprint output */

int in_from_keyb;        /* stdin appears to be from keyboard */
int have_console = FALSE;  // is there a console window yet?
int screen_col = 1;      /* column on screen, needed by expand_tabs below
                         initialized in InitGraphics, then again in InitOutput */
int screen_line = 1;     /* only used by ANSI code Linux */
int wrap_around = 1;

/*******************/
/* Local variables */
/*******************/

#ifdef ELINUX
// we need to record everything written to the screen
struct char_cell screen_image[MAX_LINES][MAX_COLS];
#endif
#ifdef EWINDOWS
static CHAR_INFO line_buffer[80]; // need more if we support graphics modes

static COORD buff_size;
static COORD buff_start;

static SMALL_RECT screen_loc;
#endif

static char expanded_string[MAX_SCREEN_WIDTH];

static char *expanded_ptr = expanded_string;

static int collect_free;   /* number of chars of empty space remaining */
static int collect_next;   /* place to store next collect output */
static int err_to_screen;  /* stderr is going to screen (always TRUE) */
static int must_flush = TRUE; /* flush output to screen or not */
static int out_to_screen;  /* stdout is going to screen */

/**********************/
/* Declared functions */
/**********************/

char *EMalloc();
char *ERealloc();

static void expand_tabs();

void SetPosition();

/*********************/
/* Defined functions */
/*********************/

#ifdef ELINUX
struct rccoord _gettextposition()
{
    struct rccoord p;

    p.row = screen_line;
    p.col = screen_col;
    return p;
}

void screen_copy(struct char_cell a[MAX_LINES][MAX_COLS],
                 struct char_cell b[MAX_LINES][MAX_COLS])
// copy a screen to another area
{
    int i, j;

    for (i = 0; i < line_max; i++) {
        for (j = 0; j < col_max; j++) {
            b[i][j] = a[i][j];  // structure copy
        }
    }
}

void screen_show()
// display a screen
{
    int i, j;

    for (i = 0; i < line_max; i++) {
        SetPosition(i+1, 1);
        for (j = 0; j < col_max; j++) {
            SetTColor(screen_image[i][j].fg_color);
            SetBColor(screen_image[i][j].bg_color);
            fputc(screen_image[i][j].ascii, stdout);
        }
    }
    fflush(stdout);
}
#endif

void InitInOut()
/* Set up stdout and stderr. In EWINDOWS some stuff
   is initialized right away. The rest is done later if necesssary on first
   use of the console - see show_console() below. */
{
    struct rccoord position;
    int i, j;

    position.col = 1;
    position.row = 1;

#ifdef EWINDOWS
    screen_line = position.row;
    screen_col = position.col;

    buff_size.Y = 1;
    buff_start.X = 0;
    buff_start.Y = 0;
#elif ELINUX
    in_from_keyb  = isatty(0);
    out_to_screen = isatty(1);
    err_to_screen = isatty(2);
    screen_line = position.row;
    screen_col = position.col;

    for (i = 0; i < line_max; i++) {
        for (j = 0; j < col_max; j++) {
            screen_image[i][j].ascii = ' ';
            screen_image[i][j].fg_color = 15;
            screen_image[i][j].bg_color = 0;
        }
    }
#endif
}

#ifdef EWINDOWS
void show_console()
/* set up a console window if not done yet */
{
#ifdef EWINDOWS
    CONSOLE_SCREEN_BUFFER_INFO info;
    CONSOLE_CURSOR_INFO c;
    INPUT_RECORD pbuffer;
    DWORD junk;
#else
    int f, b;
#endif
    if (!have_console) {
        have_console = TRUE;
#ifdef ELINUX
        initscr();  // maybe use newterm() to avoid screen clearing
        ESCDELAY=0;
        if (has_colors())
            start_color();
        cbreak();  // chars are immediately available without CR
        //noecho();
        //nonl();
        scrollok(stdscr, FALSE);
        intrflush(stdscr, FALSE);
        keypad(stdscr, TRUE);
        for (f = 0; f < config.numcolors; f++) {
            for (b = 0; b < config.numcolors; b++) {
                if (f != 0 || b != 0)
                    init_pair((f << 3) + b, f, b);
            }
        }
        NewConfig();
#else
        AllocConsole();

        console_output = GetStdHandle(STD_OUTPUT_HANDLE);

        console_trace = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
                                          FILE_SHARE_READ | FILE_SHARE_WRITE,
                                          NULL,
                                          CONSOLE_TEXTMODE_BUFFER,
                                          NULL);

        console_var_display = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
                                          FILE_SHARE_READ | FILE_SHARE_WRITE,
                                          NULL,
                                          CONSOLE_TEXTMODE_BUFFER,
                                          NULL);

        c.dwSize = 12;
        c.bVisible = FALSE;
        SetConsoleCursorInfo(console_trace, &c);

        out_to_screen = GetConsoleScreenBufferInfo(console_output, &info);

        if (!out_to_screen) {
            console_output = CreateFile("CONOUT$",
                                    GENERIC_READ | GENERIC_WRITE,
                                    FILE_SHARE_WRITE,
                                    NULL,
                                    OPEN_EXISTING,
                                    0,
                                    NULL);
        }

        console_input = GetStdHandle(STD_INPUT_HANDLE);

        in_from_keyb = PeekConsoleInput(console_input, &pbuffer, 1, &junk);

        // This stops the mouse cursor from appearing in full screen
        SetConsoleMode(console_input, ENABLE_LINE_INPUT |
                                ENABLE_ECHO_INPUT |
                                ENABLE_PROCESSED_INPUT);  // no mouse please
#endif
    }
}
#endif

#ifdef EWINDOWS
static void end_of_line(int c)
// handle \n or \r in Windows console
{
    CONSOLE_SCREEN_BUFFER_INFO console_info;

    GetConsoleScreenBufferInfo(console_output, &console_info); // not always necessary?
    console_info.dwCursorPosition.X = 0;
    if (c == '\n') {
        if (console_info.dwCursorPosition.Y < console_info.dwSize.Y - 1)
            console_info.dwCursorPosition.Y++;
        else {
            // scroll screen up one line
            SMALL_RECT src, clip;
            COORD dest;
            CHAR_INFO fill_char;

            src.Left = 0;
            src.Right = console_info.dwSize.X; //79;  // assume 80-char screen for now
            src.Top = 0;
            src.Bottom = console_info.dwSize.Y-1; // -1 ???
            clip = src;
            dest.X = 0;
            dest.Y = src.Top - 1; // for now - ok???
            fill_char.Char.AsciiChar = ' ';
            fill_char.Attributes = console_info.wAttributes;
            ScrollConsoleScreenBuffer(console_output,
                                      &src,
                                      &clip,
                                      dest,
                                      &fill_char);
        }
    }
    SetConsoleCursorPosition(console_output, console_info.dwCursorPosition);
}

#if defined(ELCC) || defined(EBORLAND)
int MyReadConsoleChar()
// Read the next character typed by the user on the console
{
    DWORD nread;
    char buff[4];

    ReadConsole(console_input, buff, 1, &nread, NULL);
    return buff[0];
}
#endif

static void MyWriteConsole(char *string, int nchars)
// write a string of plain characters to the console and
// update the cursor position
{
    CONSOLE_SCREEN_BUFFER_INFO console_info;
    int i;

    show_console();
    GetConsoleScreenBufferInfo(console_output, &console_info); // not always necessary?

    buff_size.X = nchars;

    screen_loc.Top = console_info.dwCursorPosition.Y;
    screen_loc.Bottom = screen_loc.Top;
    screen_loc.Left = console_info.dwCursorPosition.X; //screen_col-1;
    screen_loc.Right = 79;

    i = 0;
    while (*string != '\0') {
        line_buffer[i].Char.AsciiChar = *string;
        line_buffer[i].Attributes = console_info.wAttributes;
        string++;
        i++;
    }
    WriteConsoleOutput(console_output,
                       line_buffer, // was:  &line_buffer ?
                       buff_size,
                       buff_start,
                       &screen_loc);

    console_info.dwCursorPosition.X += nchars; // what if becomes 80? (i.e 1 too big)
    SetConsoleCursorPosition(console_output, console_info.dwCursorPosition);
    //screen_col += nchars;
}
#endif

void buffer_screen()
/* start buffering the screen output on each line */
{
    must_flush = FALSE;
}

void flush_screen()
/* flush any left over characters */
{
    must_flush = TRUE;
    expand_tabs("");
}

#ifdef ELINUX
void update_screen_string(char *s)
// record that a string of characters was written to the screen
{
    int i, col, line;
    char buff[60];

    i = 0;
    line = screen_line - 1;
    col = screen_col - 1;
    if (line < 0 || line >= line_max) {
        sprintf(buff, "line corrupted (%d), s is %s, col is %d",
        line, s, col);
        debug_msg(buff);
    }
    // we shouldn't get any \n's or \r's, but just in case:
    while (s[i] != 0 && s[i] != '\n' && s[i] != '\r' && col < col_max) {
        screen_image[line][col].ascii = s[i];
        screen_image[line][col].fg_color = current_fg_color;
        screen_image[line][col].bg_color = current_bg_color;
        col += 1;
        if (col < 0 || col > col_max) {
            sprintf(buff, "col corrupted (%d)", col);
            debug_msg(buff);
        }
        i += 1;
    }
}
#endif

static void expand_tabs(char *raw_string)
/* Expand tabs and truncate long lines.
 * Still needed to avoid a WATCOM bug that misprints lines that
 * are too long for the screen width. Debug/Trace screen still uses
 * tab expansion feature. Flush feature is used for better performance.
 * Note: screen_col is the column based on what we have actually written
 * to the screen so far, not what we have buffered.
 */
{
    int c, i, nblanks, true_screen_col;

    while ((c = *raw_string++) != 0) {

        if (screen_col + (expanded_ptr - expanded_string) > col_max) {
            // going past right margin
            if (wrap_around) {
                /* what if 0 chars to write? */
                *expanded_ptr = '\0';
#ifdef EWINDOWS
                MyWriteConsole(expanded_string,
                               expanded_ptr - expanded_string);
                end_of_line('\n');
#elif ELINUX
                fputs(expanded_string, stdout);
                fflush(stdout);
                update_screen_string(expanded_string);
#endif
                screen_col = 1;
                expanded_ptr = expanded_string; // make it empty
            }
            else {
                if (c != '\n' && c != '\r')
                    continue; // ignore stuff past right margin
            }
        }

        if (c == '\t') {
            true_screen_col = screen_col + expanded_ptr - expanded_string;
            /* expand with blanks */
            nblanks = ((true_screen_col - 1) & ~(TAB_WIDTH - 1)) +
                      (TAB_WIDTH + 1) - true_screen_col;
            if (true_screen_col + nblanks > col_max)
                nblanks = col_max - true_screen_col + 1;
            for (i = 1; i <= nblanks; i++) {
                *expanded_ptr++ = ' ';
            }
        }

        else if (c == '\n' || c == '\r') {
#ifdef EWINDOWS
            MyWriteConsole(expanded_string, expanded_ptr - expanded_string);
            end_of_line(c);
#endif

#ifdef ELINUX
            // curses advances to next line if given \r or \n beyond 80
            *expanded_ptr = '\0';
            fputs(expanded_string, stdout);
            fflush(stdout);
            update_screen_string(expanded_string);
            fputc(c, stdout);
            fflush(stdout);
#endif

            screen_col = 1;
            if (c == '\n' && screen_line < config.numtextrows-1)
                screen_line += 1;

            expanded_ptr = expanded_string;
        }

        else if (screen_col <= col_max) {
            // normal characters
            *expanded_ptr++ = c;
        }
    } // end while

    /* left over characters - flush? */
    if (expanded_ptr != expanded_string && must_flush) {

        *expanded_ptr = '\0';
#ifdef EWINDOWS
        MyWriteConsole(expanded_string, expanded_ptr - expanded_string);
#elif ELINUX
        fputs(expanded_string, stdout);
        fflush(stdout);
        update_screen_string(expanded_string);
#endif
        screen_col += expanded_ptr - expanded_string;
        expanded_ptr = expanded_string;
    }
}

void screen_output(FILE *f, char *out_string)
/* All output from the compiler, interpreter or user program
   comes here (except for some EPuts() output). It is then directed to the
   appropriate window or passed to a file. */
/* f is output file, or NULL if debug screen, or DOING_SPRINTF */
/* out_string is null-terminated string of characters to write out */
{
    int len;

    if ((int)f == DOING_SPRINTF) {
        /* save characters as a C string in memory */
        len = strlen(out_string);
        if (collect == NULL) {
            collect_free = 80;
            collect = EMalloc(len+1+collect_free);
            strcpy(collect, out_string);
            collect_next = len;
        }
        else {
            if (len > collect_free) {
                collect_free = len + 200;
                collect = ERealloc(collect, collect_next + 1 + collect_free);
            }
            strcpy(collect+collect_next, out_string);
            collect_free -= len;
            collect_next += len;
        }
    }

    else if (f == NULL)
        /* send to debug screen */
        expand_tabs(out_string);

    else {
#ifdef ELINUX
        if ((f == stdout && out_to_screen) ||
            (f == stderr && err_to_screen)) {
            if (current_screen != MAIN_SCREEN)
                MainScreen();
            expand_tabs(out_string);
            return;
        }
#else
        if (f == stdout || f == stderr) {
#ifdef EWINDOWS
            show_console();  // needed to initialize out_to_screen in WIN32
#endif
            if (current_screen != MAIN_SCREEN)
                MainScreen();
            if (out_to_screen || f == stderr) {  //stderr always goes to screen
                expand_tabs(out_string);
                return;
            }
        }
#endif
        else {
            /* file/device output - should flush some devices ? */
            if (current_screen != MAIN_SCREEN && con_was_opened)
                MainScreen();
        }

        fputs(out_string, f);
    }
}

#ifdef EWINDOWS
void EClearLines(int first_line, int last_line, int len, WORD attributes)
{
    int i, n;
    COORD origin;

    origin.X = 0;
    for (i = first_line; i <= last_line; i++) {
         origin.Y = i - 1;
         FillConsoleOutputCharacter(console_output, ' ', len, origin, (LPDWORD)&n);
         FillConsoleOutputAttribute(console_output, attributes, len, origin, (LPDWORD)&n);
    }
}
#endif

void ClearScreen()
{
#ifdef EWINDOWS
    CONSOLE_SCREEN_BUFFER_INFO info;

    show_console();
    GetConsoleScreenBufferInfo(console_output, &info);
    EClearLines(1, info.dwSize.Y, info.dwSize.X, info.wAttributes);
    SetPosition(1,1);
#endif

#ifdef ELINUX
    // ANSI code
    fputs("\033[2J", stdout);  // clear screen
    SetPosition(1,1);
#endif

    screen_line = 1;
    screen_col = 1;
}

void SetPosition(int line, int col)
{
#ifdef ELINUX
    char lbuff[20];
    char cbuff[20];

    sprintf(lbuff, "%d", line);
    sprintf(cbuff, "%d", col);
    // ANSI code
    fputs("\033[", stdout);
    fputs(lbuff, stdout);
    fputc(';', stdout);
    fputs(cbuff, stdout);
    fputc('H', stdout);
    fflush(stdout);
#endif

#ifdef EWINDOWS
    COORD pos;

    pos.X = col-1;
    pos.Y = line-1;
    show_console();
    SetConsoleCursorPosition(console_output, pos);
#endif
    screen_col = col;
    screen_line = line;
}

